﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TSTree.fuctions;
using MathNet.Numerics.Distributions;

namespace TSTree
{
    public static class Utilities
    {

        #region computes the DIST between 1 pt and 1 MBR
        /**
     * computes the MAXDIST between 1 pt and 1 MBR. It is defined as the
     * maximum distance of a MBR vertex against the specified point
     * Used as an upper bound of the furthest rectangle inside an MBR from a specific
     * point
     */
        public static float MAXDIST(TSPoint point, float[] bounces)
        {

            float sum = (float)0.0;
            float maxdiff;
            int i;

            for (i = 0; i < point.dimension; i++)
            {

                maxdiff = Math.Max(Math.Abs(point.data[i] - bounces[2 * i]), Math.Abs(point.data[i] - bounces[2 * i + 1]));
                sum += (float)Math.Pow(maxdiff, 2.0);
            }
            return (sum);
        }
        /**
   * computes the MINDIST between 1 pt and 1 MBR (see [Rous95])
   * the MINDIST ensures that the nearest neighbor from this pt to a
   * rect in this MBR is at at least this distance
   */
        public static float MINDIST(TSPoint pt, float[] bounces)
        {
            TSPoint point = (TSPoint)pt;

            float sum = (float)0.0;
            float r;
            int i;

            for (i = 0; i < point.dimension; i++)
            {
                if (point.data[i] < bounces[2 * i])
                    r = bounces[2 * i];
                else
                {
                    if (point.data[i] > bounces[2 * i + 1])
                        r = bounces[2 * i + 1];
                    else
                        r = point.data[i];
                }

                sum += (float)Math.Pow((double)point.data[i] - r, 2);
            }
            return (sum);
        }
        /**
   * computes the MINMAXDIST between 1 pt and 1 MBR (see [Rous95])
   * the MINMAXDIST ensures that there is at least 1 object in the MBR
   * that is at most MINMAXDIST far away of the point
   */
        public static float MINMAXDIST(TSPoint point, float[] bounces)
        {

            float sum = 0;
            float minimum = (float)1.0e20;
            float S = 0;

            float rmk, rMi;
            int k, i, j;

            for (i = 0; i < point.dimension; i++)
            {
                rMi = (point.data[i] >= (bounces[2 * i] + bounces[2 * i + 1]) / 2) ? bounces[2 * i] : bounces[2 * i + 1];
                S += (float)Math.Pow(point.data[i] - rMi, 2);
            }

            for (k = 0; k < point.dimension; k++)
            {
                rmk = (point.data[k] <= (bounces[2 * k] + bounces[2 * k + 1]) / 2) ? bounces[2 * k] : bounces[2 * k + 1];

                sum = (float)Math.Pow(point.data[k] - rmk, 2);

                rMi = (point.data[k] >= (bounces[2 * k] + bounces[2 * k + 1]) / 2) ? bounces[2 * k] : bounces[2 * k + 1];

                sum += S - (float)Math.Pow(point.data[k] - rMi, 2);

                minimum = Math.Min(minimum, sum);
            }

            return (minimum);
        }

        #endregion

        #region intersect

        /**
    * returns true if the specified mbr intersects the specified circle
    */
        public static Boolean isCircleInterSection(int dimension, float[] mbr1, TSPoint center, float radius)
        {
            float r2;

            r2 = radius * radius;

            // if MBR contains circle center (MINDIST) return true
            // if r2>MINDIST return true
            //if ((MINDIST(center,mbr1) != 0) ||
            //    (((r2 - MINDIST(center,mbr1)) < (float)1.0e-8)))
            if ((r2 - MINDIST(center, mbr1)) < (float)1.0e-8)
                return false;
            else
                return true;
        }
        /**
       * returns true if the specified mbr intersects the specified circle
       */
        public static Boolean isRingInterSection(int dimension, float[] mbr1, TSPoint center, float radius1, float radius2)
        {
            float r_c1; //inner circle radius
            float r_c2; //outer circle radius

            if (radius1 < radius2)
            {
                r_c1 = radius1 * radius1;
                r_c2 = radius2 * radius2;
            }
            else
            {
                r_c1 = radius2 * radius2;
                r_c2 = radius1 * radius1;
            }

            // if MBR contains circle center (MINDIST) return true
            // if r2>MINDIST return true
            //if ((MINDIST(center,mbr1) != 0) ||
            //    (((r2 - MINDIST(center,mbr1)) < (float)1.0e-8)))
            if (((r_c1 - MAXDIST(center, mbr1)) < (float)1.0e-8) && ((MINDIST(center, mbr1) - r_c2) < (float)1.0e-8))
                return true;
            else
                return false;
        }
        /**
    * returns true if the two mbrs intersect
    */
        public static Boolean isRectInterSection(int dimension, float[] mbr1, float[] mbr2)
        {
            int i;

            for (i = 0; i < dimension; i++)
            {
                if (mbr1[2 * i] > mbr2[2 * i + 1] || mbr1[2 * i + 1] < mbr2[2 * i])
                    return false;
            }
            return true;
        }
        public static void processError(String msg, bool fatal)
        {
            Console.WriteLine(msg);
            if (fatal) Environment.Exit(1);
        }

        /************************************************************************/
        /*  Returns the d-dimension area of the mbr                                                                      */
        /************************************************************************/
        public static float computeArea(int dimension, float[] mbr)
        {
            int i;
            float sum;

            sum = (float)1.0;
            for (i = 0; i < dimension; i++)
                sum *= mbr[2 * i + 1] - mbr[2 * i];

            return sum;
        }
        /************************************************************************/
        /*  Enlarge r1 (to mbr) in a way that it contains r1 and r2                                                                 */
        /************************************************************************/
        public static void enlargeMBR(int dimension, float[] mbr, float[] r1, float[] r2)
        {
            int i;

            for (i = 0; i < 2 * dimension; i += 2)
            {
                mbr[i] = Math.Min(r1[i], r2[i]);
                mbr[i + 1] = Math.Max(r1[i + 1], r2[i + 1]);
            }
        }
        /************************************************************************/
        /* Calculates the overlapping area of r1 and r2 
         * Calculate overlap in every dimension and multiplicative the values                                                                      */
        /************************************************************************/
        public static float calcOverlapArea(int dimension, float[] r1, float[] r2)
        {
            float sum;
            int r1pos, r2pos, r1last;
            float r1_lb, r1_ub, r2_lb, r2_ub;

            sum = (float)1.0;
            r1pos = 0; r2pos = 0;
            r1last = 2 * dimension;

            while (r1pos < r1last)
            {
                r1_lb = r1[r1pos++];
                r1_ub = r1[r1pos++];
                r2_lb = r2[r2pos++];
                r2_ub = r2[r2pos++];

                // calculate overlap in this dimension

                if (isInside(r1_ub, r2_lb, r2_ub))
                // upper bound of r1 is inside r2
                {
                    if (isInside(r1_lb, r2_lb, r2_ub))
                        // and lower bound of r1 is inside
                        sum *= (r1_ub - r1_lb);
                    else
                        sum *= (r1_ub - r2_lb);
                }
                else
                {
                    if (isInside(r1_lb, r2_lb, r2_ub))
                        // and lower bound of r1 is inside
                        sum *= (r2_ub - r1_lb);
                    else
                    {
                        if (isInside(r2_lb, r1_lb, r1_ub) && isInside(r2_ub, r1_lb, r1_ub))
                            // r1 contains r2
                            sum *= (r2_ub - r2_lb);
                        else
                            // r1 and r2 do not overlap
                            sum = (float)0.0;
                    }
                }
            }
            return sum;
        }

        // Is a vector in a box?
        public static Boolean isInside(float[] v, float[] mbr, int dimension)
        {
            int i;

            for (i = 0; i < dimension; i++)
                if (!isInside(v[i], mbr[2 * i], mbr[2 * i + 1]))
                    return false;
            return true;
        }

        //Is a scalar in an interval?
        public static Boolean isInside(float p, float lb, float ub)
        {
            return (p >= lb && p <= ub);
        }

        /** This is a generic version of C.A.R Hoare's Quick Sort
        * algorithm.  This will handle arrays that are already
        * sorted, and arrays with duplicate keys.
        *
        * If you think of a one dimensional array as going from
        * the lowest index on the left to the highest index on the right
        * then the parameters to this function are lowest index or
        * left and highest index or right.  The first time you call
        * this function it will be with the parameters 0, a.length - 1.
        *
        * @param a       a ISortable array
        * @param lo0     left boundary of array partition
        * @param hi0     right boundary of array partition
        */
        public static void quickSort(ISortable[] a, int lo0, int hi0, int sortCriterion)
        {
            int lo = lo0;
            int hi = hi0;
            ISortable mid;

            if (hi0 > lo0)
            {
                /* Arbitrarily establishing partition element as the midpoint of
            * the array.
            */
                mid = a[(lo0 + hi0) / 2];

                // loop through the array until indices cross
                while (lo <= hi)
                {
                    /* find the first element that is greater than or equal to
                     * the partition element starting from the left Index.
                     */
                    while ((lo < hi0) && (a[lo].lessThan(mid, sortCriterion)))
                        ++lo;

                    /* find an element that is smaller than or equal to
                     * the partition element starting from the right Index.
                     */
                    while ((hi > lo0) && (a[hi].greaterThan(mid, sortCriterion)))
                        --hi;

                    // if the indexes have not crossed, swap
                    if (lo <= hi)
                    {
                        swap(a, lo, hi);
                        ++lo;
                        --hi;
                    }
                }

                /* If the right index has not reached the left side of array
                 * must now sort the left partition.
                 */
                if (lo0 < hi)
                    quickSort(a, lo0, hi, sortCriterion);

                /* If the left index has not reached the right side of array
                 * must now sort the right partition.
                 */
                if (lo < hi0)
                    quickSort(a, lo, hi0, sortCriterion);
            }
        }
        //Swaps two entries in an array of objects to be sorted.
        private static void swap(ISortable[] a, int i, int j)
        {
            ISortable T;
            T = a[i];
            a[i] = a[j];
            a[j] = T;

        }
        /************************************************************************/
        /*Returns the margin of the mbr. That is the sum of all projections to the axes                                                                      */
        /************************************************************************/
        public static float margin(int dimension, float[] mbr)
        {
            int i;
            int ml, mu, m_last;
            float sum;

            sum = (float)0.0;
            m_last = 2 * dimension;
            ml = 0;
            mu = ml + 1;
            while (mu < m_last)
            {
                sum += mbr[mu] - mbr[ml];
                ml += 2;
                mu += 2;
            }

            return sum;
        }

        #endregion

        #region Euclidean distance between 2 points

   /**
    * computes the square of the Euclidean distance between 2 points
    */
        public static float objectDIST(TSPoint point1, TSPoint point2)
        {

            float sum = (float)0;
            int i;

            for (i = 0; i < point1.dimension; i++)
                sum += (float)Math.Pow(point1.data[i] - point2.data[i], 2);

            //return( sqrt(sum) );
            return (sum);
        }
        public static float objectEuclideDIST(TSPoint point1, TSPoint point2)
        {

            float sum = (float)0;
            int i;

            for (i = 0; i < point1.dimension; i++)
                sum += (float)Math.Pow(point1.data[i] - point2.data[i], 2);

            return ((float)Math.Sqrt(sum));
            //return (sum);
        }

        #endregion 

        #region BranchList
        public static int pruneBranchList(float nearest_distance/*[]*/, Object[] activebranchList, int n)
        {

            // Schneidet im Array BranchList alle Eintraege ab, deren distance groesser
            // ist als die aktuell naeheste distance
            //

            BranchList[] bl;

            int i, j, k, aktlast;

            bl = (BranchList[])activebranchList;

            // 1. Strategie:
            //
            // Ist MINDIST(P,M1) > MINMAXDIST(P,M2), so kann der
            // NearestNeighbor auf keinen Fall mehr in M1 liegen!
            //

            aktlast = n;

            for (i = 0; i < aktlast; i++)
            {
                if (bl[i].minmaxdist < bl[aktlast - 1].mindist)
                    for (j = 0; (j < aktlast); j++)
                        if ((i != j) && (bl[j].mindist > bl[i].minmaxdist))
                        {
                            aktlast = j;
                            break;
                        }
            }

            // 2. Strategie:
            //
            // nearest_distance > MINMAXDIST(P,M)
            // -> nearest_distance = MIMMAXDIST(P,M)
            //

            for (i = 0; i < aktlast; i++)
                if (nearest_distance/*[0]*/ > bl[i].minmaxdist)
                    nearest_distance/*[0]*/ = bl[i].minmaxdist;


            // 3. Strategie:
            //
            // nearest_distance < MINDIST(P,M)
            //
            // in M kann der Nearest-Narbor sicher nicht mehr liegen.
            //

            for (i = 0; (i < aktlast) && (nearest_distance/*[0]*/ >= bl[i].mindist); i++) ;

            aktlast = i;

            // printf("n: %d aktlast: %d \n",n,aktlast);

            return (aktlast);
        }
        public static int testBranchList(Object[] abL, Object[] sL, int n, int last)
        {

            // Schneidet im Array BranchList alle Eintr"age ab, deren distance gr"o"ser
            // ist als die aktuell naeheste distance
            //

            BranchList[] activebranchList, sectionList;

            int i, number, aktlast;

            activebranchList = (BranchList[])abL;
            sectionList = (BranchList[])sL;

            aktlast = last;

            for (i = last; i < n; i++)
            {
                number = activebranchList[i].entry_number;
                if (sectionList[number].section)
                {
                    // obwohl vom Abstand her dieser Eintrag gecanceld werden
                    // m"usste, wird hier der Eintrag in die ABL wieder
                    // aufgenommen, da evtl. ein Treffer der range-Query zu erwarten ist!
                    //
                    // An der letzten Stelle der Liste kommt der aktuelle Eintrag!
                    aktlast++;
                    activebranchList[aktlast].entry_number = activebranchList[i].entry_number;
                    activebranchList[aktlast].mindist = activebranchList[i].mindist;
                    activebranchList[aktlast].minmaxdist = activebranchList[i].minmaxdist;
                }

            }

            return (aktlast);

        }

        #endregion

        #region SAX
        public static char[] SaxTranform(float[] data, int max, int min, int length, int dimensions)
        {
            char[] sax = new char[dimensions];
            char[] symbols = new char[length];
            int interval = 0;
            for (int i = 0; i < length; i++)
            {
                //Console.WriteLine(Convert.ToChar(i + 65));
                symbols[i] = Convert.ToChar(i + 33);
            }

            interval = (max - min) / length;
            for (int i = 0; i < dimensions; i++)
            {
                    if (data[i] < min)
                    {
                        sax[i] = symbols[0];
                    }
                    else if (data[i] > max)
                    {
                        sax[i] = symbols[symbols.Length - 1];
                    }
                    else{
                            for (int j = 0; j < symbols.Length; j++)
                            {
                                if (data[i] >= min + interval * j && data[i] <= min + interval * (j + 1)) 
                                {
                                    sax[i] = symbols[j];
                                    break; 
                                }
                            }
                        }

            }
            return sax;
        }

        public static char[] SaxTranform(double[] data, int max, int min, int length, int dimensions)
        {
            char[] sax = new char[dimensions];
            char[] symbols = new char[length];
            int interval = 0;
            for (int i = 0; i < length; i++)
            {
               // Console.WriteLine(Convert.ToChar(i + 65));
                symbols[i] = Convert.ToChar(i + 33);
            }

            interval = (max - min) / length;

            for (int i = 0; i < dimensions; i++)
            {
                if (data[i] < min)
                {
                    sax[i] = symbols[0];
                }
                else if (data[i] > max)
                {
                    sax[i] = symbols[symbols.Length - 1];
                }
                else
                {
                    for (int j = 0; j < symbols.Length; j++)
                    {
                        if (data[i] >= min + interval * j && data[i] <= min + interval * (j + 1))
                        {
                            sax[i] = symbols[j];
                            break;
                        }
                    }
                }

            }
            return sax;
        }
        public static double[] setGaussianTable(int length)
        {
            double[] d;
            d = new double[length - 1];
            for (int i = 0; i < length - 1; i++)
            {
                Normal normalDist = new Normal();
                d[i] = normalDist.InverseCumulativeDistribution((float)(i + 1) / length);
            }
            return d;
        }

        private static int GetIndexInAlphabet(char value)
        {
            // Uses the uppercase character unicode code point. 'A' = U+0042 = 65, 'Z' = U+005A = 90
            char upper = value; // char.ToUpper(value);
            if ((int)upper < 33 || (int)upper > 126)
            {
                throw new ArgumentOutOfRangeException("value", "This method only accepts standard Latin characters.");
            }

            return (int)upper - (int)'!';
        }

        public static double SaxDistance(char[] c1, char[] c2, int length)
        {
            double t = 0;
            double[] temp;
            temp = new double[length - 1];

            if (c1.Length != c2.Length)
            {
                Console.WriteLine("Length is not the same !");
            }

            Array.Copy(setGaussianTable(length), temp, length - 1);

            for (int i = 0; i < c1.Length; i++)
            {
                int c1Index = GetIndexInAlphabet(c1[i]);
                int c2Index = GetIndexInAlphabet(c2[i]);

                if (Math.Abs((c1Index + 1) - (c2Index + 1)) <= 1)
                {
                    t += 0;
                }
                else if ((c1Index + 1) < (c2Index + 1) - 1)
                {
                    t += Math.Pow(temp[c2Index - 1] - temp[c1Index], 2);
                }
                else if ((c1Index + 1) > (c2Index + 1) - 1)
                {
                    t += Math.Pow(temp[c1Index - 1] - temp[c2Index], 2);
                }
                Console.WriteLine(t.ToString());
            }
            return Math.Sqrt(t);

        }

        public static double SaxDistance(char c1, char c2, int length)
        {
            double t = 0;
            double[] temp;
            temp = new double[length - 1];

            Array.Copy(setGaussianTable(length), temp, length - 1);

                int c1Index = GetIndexInAlphabet(c1);
                int c2Index = GetIndexInAlphabet(c2);

                if (Math.Abs((c1Index + 1) - (c2Index + 1)) <= 1)
                {
                    t += 0;
                }
                else if ((c1Index + 1) < (c2Index + 1) - 1)
                {
                    t += Math.Pow(temp[c2Index - 1] - temp[c1Index], 2);
                }
                else if ((c1Index + 1) > (c2Index + 1) - 1)
                {
                    t += Math.Pow(temp[c1Index - 1] - temp[c2Index], 2);
                }
                Console.WriteLine(t.ToString());
            
            return Math.Sqrt(t);

        }

        public static float DTWdistance(int dimension, int startIndex, float[] point, float[] bounces)
        {
            float f = 0;
            for (int i = startIndex * 2; i < 2 * dimension; i += 2)
            {
                if (point[i / 2] < bounces[i])
                {
                    // < lower bounces
                    f += (float)Math.Pow(point[i / 2] - bounces[i], 2);
                }
                else if (point[i / 2] > bounces[i + 1])
                {
                    //> upper bounce
                    f += (float)Math.Pow(point[i / 2] - bounces[i+1], 2);
                }
                else
                {
                    //0
                    f += 0;
                }
            }
                return (float)Math.Sqrt(f);
        }
      
        #endregion


    }
}
